<!doctype html>
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
  <meta charset="utf-8">
  <script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
  <script src="../../../web-component-tester/browser.js"></script>
  <link rel="import" href="../../polymer.html">
</head>
<body>

  <x-scope></x-scope>

  <dom-module id="story-card">

    <style>
      :host {
        display: block;
      }

      #story-card .story-content {
        font-family: 'Roboto', sans-serif;
        font-size: 1.2em;
        @apply(--story-content);
      }
    </style>

    <template>
      <div id="story-card">
        <div class="story-content" id="content">Content</div>
      </div>
    </template>
    <script>
    HTMLImports.whenReady(function() {
      Polymer({is: 'story-card'});
    });
    </script>
  </dom-module>

  <dom-module id="x-child-scope">
    <style>
      :host {
        display: block;

        --mixin2: {
          border: 3px solid seagreen;
          background: url(http://www.google.com/icon.png);
        };
      }

      #mixin1 {
        @apply(--mixin1);
      }

      #mixin2 {
        @apply(--mixin2);
      }

      #mixin3 {
        padding: 3px;
        @apply(--mixin3);
      }

      #mixin4 {
        @apply(--mixin4);
      }

      #mixin6 {
        @apply(--mixin6)
      }

      #mixin7 {@apply(--mixin7)}

      #mixin8 {
        @apply --mixin8
      }

      #mixin9 {
        @apply --mixin9
      }
    </style>

    <template>
      <div id="mixin1">mixin1</div>
      <div id="mixin2">mixin2</div>
      <div id="mixin3">mixin3</div>
      <div id="mixin4">mixin4</div>
      <div id="mixin6">mixin6</div>
      <div id="mixin7">mixin7</div>
      <div id="mixin8">mixin8</div>
      <div id="mixin9">mixin9</div>
    </template>
    <script>
    HTMLImports.whenReady(function() {
      Polymer({is: 'x-child-scope'});
    });
    </script>
  </dom-module>

  <dom-module id="x-keyframes">
    <template>
      <style>
        :host {
          display: block;
          position: relative;
          border: 10px solid blue;
          left: 0px;
          /* Prefix required by Safari <= 8 */
          -webkit-animation-duration: 0.3s;
          animation-duration: 0.3s;
          -webkit-animation-fill-mode: forwards;
          animation-fill-mode: forwards;
        }

        :host([animated]) {
          /* Prefix required by Safari <= 8 */
          -webkit-animation-name: x-keyframes-animation;
          animation-name: x-keyframes-animation;
        }

        /* Prefix required by Safari <= 8 */
        @-webkit-keyframes x-keyframes-animation {
          0% {
            left: var(--c1);
          }

          100% {
            left: var(--c2);
            @apply(--keyframe-finish);
          }
        }
        @keyframes x-keyframes-animation {
          0% {
            left: var(--c1);
          }

          100% {
            left: var(--c2);
            @apply(--keyframe-finish);
          }
        }
      </style>
      x-keyframes
    </template>
    <script>
    HTMLImports.whenReady(function() {
      Polymer({
        is: 'x-keyframes',
        properties: {
          animated: {
            type: Boolean,
            value: false,
            reflectToAttribute: true
          }
        }
      });
    });
    </script>
  </dom-module>

  <dom-module id="x-scope">
    <style>
      :host {
        display: block;
        padding: 8px;

        --override-me: {
          border: 11px solid black;
        };

        --mixin1: {
          border: 1px solid black;
        };

        --b: 2px solid orange;

        --m1: 1px;
        --m2: 2px;

        --c1: 5px;
        --c2: 10px;

        --mixin2: {
          border: var(--b);
        };

        --mixin3: {
          @apply(--mixin2);
        };

        --mixin4: {
          padding: 2px;
          @apply(--mixin3);
          margin: var(--m2);
        };

        --mixin5: {
          border: calc(var(--c1) + var(--c2)) solid orange;
        };

        --mixin6: {
          border: 16px solid orange;
        }

        --mixin7: {
          border: 17px solid navy;
        }

        --mixin8: {
          border: 17px dotted navy;
        };

        --mixin9: var(--mixin8);
      }

      #mixin1 {
        @apply(--mixin1);
      }

      #mixin2 {
        @apply(--mixin2);
      }

      #mixin3 {
        padding: 1px;
        margin: var(--m1);
        @apply(--mixin3);
      }

      #mixin4 {
        @apply(--mixin4);
      }

      #mixin5 {
        @apply(--mixin5);
      }

      #keyframes2 {
        --keyframe-finish: {
          left: 20px;
        };
      }

      x-child-scope {
        padding: 10px;
      }

      #card {
        --story-content: {
          border: 11px solid orange;
        };
      }

      #override {
        @apply(--override-me);
        border: 19px solid steelblue;
      }

    </style>

    <template>
      <div id="mixin1">mixin1</div>
      <div id="mixin2">mixin2</div>
      <div id="mixin3">mixin3</div>
      <div id="mixin4">mixin4</div>
      <div id="mixin5">mixin5</div>
      <hr>
      <x-keyframes id="keyframes1"></x-keyframes>
      <x-keyframes id="keyframes2"></x-keyframes>
      <x-child-scope id="child"></x-child-scope>
      <story-card id="card"></story-card>
      <div id="override">override</div>
    </template>
    <script>
    HTMLImports.whenReady(function() {
      Polymer({is: 'x-scope'});
    });
    </script>
  </dom-module>

  <dom-module id="x-var-produce-via-consume">
  <template>
    <style>
      :host {
        display: block;
        border: 10px solid orange;
        --foo: {
          color: var(--bar);
        }
      }
    </style>
        </template>
  <script>
  HTMLImports.whenReady(function() {
    Polymer({
      is: 'x-var-produce-via-consume'
    });
  });
  </script>
</dom-module>

<dom-module id="x-cache-child">
  <template>
    <style>
    :host {
      display: block;
      color: var(--foo);
      @apply --bar;
    }
    </style>
    X
  </template>
  <script>
  HTMLImports.whenReady(function() {
    Polymer({
      is: 'x-cache-child'
    });
  });
  </script>
</dom-module>

<dom-module id="x-cache-host-1">
  <template>
    <style>
    :host {
      display: block;
      --foo: blue;
      --bar: {
        border: 10px solid black;
      }
    }
    </style>
    <x-cache-child id="child"></x-bad-child>
  </template>
  <script>
  HTMLImports.whenReady(function() {
    Polymer({
      is: 'x-cache-host-1'
    });
  });
  </script>
</dom-module>

<dom-module id="x-cache-host-2">
  <template>
    <style>
    :host {
      display: block;
      --foo: blue;
      --bar: {
        border: 1px dotted green;
      }
    }
    </style>
    <x-cache-child id="child"></x-bad-child>
  </template>
  <script>
  HTMLImports.whenReady(function() {
    Polymer({
      is: 'x-cache-host-2'
    });
  });
  </script>
</dom-module>

<dom-module id="x-no-paren-apply-only">
  <template>
    <style>
    :host {
      --bar: {
        border: 2px solid green;
      }
    }
    #child {
      @apply --bar;
    }
    </style>
    <div id="child">X</div>
  </template>
  <script>
  HTMLImports.whenReady(function() {
    Polymer({
      is: 'x-no-paren-apply-only'
    });
  });
  </script>
</dom-module>

<dom-module id="x-redefine-three">
  <template>
    <style>
      div {
        @apply --redefine;
      }
    </style>
    <div id="div"></div>
  </template>
  <script>
  HTMLImports.whenReady(function() {
    Polymer({is: 'x-redefine-three'});
  })
  </script>
</dom-module>

<dom-module id="x-redefine-two">
  <template>
    <style>
      x-redefine-three {
        --redefine: {
          border: 2px solid gray;
        };
      }
    </style>
    <x-redefine-three id="three"></x-redefine-three>
  </template>
  <script>
  HTMLImports.whenReady(function() {
    Polymer({is: 'x-redefine-two'});
  })
  </script>
</dom-module>

<dom-module id="x-redefine-one">
  <template>
    <style>
      x-redefine-two {
        --redefine: {
          height: 100px;
          width: 100px;
        };
      }
    </style>
    <x-redefine-two id="two"></x-redefine-two>
  </template>
  <script>
  HTMLImports.whenReady(function() {
    Polymer({is: 'x-redefine-one'});
  })
  </script>
</dom-module>

<dom-module id="x-consume-mixin">
  <template>
    <style>
    :host {
      @apply --above;
    }
    </style>
    <span>X</span>
  </template>
  <script>
  HTMLImports.whenReady(function() {
    Polymer({
      is: 'x-consume-mixin'
    })
  })
  </script>
</dom-module>

<dom-module id="x-produce-mixin-1">
  <template>
    <style>
    :host {
      --above: {
        color: rgb(255, 0, 0);
      };
    }
    </style>
    <x-consume-mixin id="child"></x-consume-mixin>
  </template>
  <script>
  HTMLImports.whenReady(function() {
    Polymer({
      is: 'x-produce-mixin-1'
    })
  })
  </script>
</dom-module>

<dom-module id="x-produce-mixin-2">
  <template>
    <style>
    :host {
      --above: {
        background-color: rgb(0, 255, 0);
      };
    }
    </style>
    <x-consume-mixin id="child"></x-consume-mixin>
  </template>
  <script>
  HTMLImports.whenReady(function() {
    Polymer({
      is: 'x-produce-mixin-2'
    })
  })
  </script>
</dom-module>

<dom-module id="x-produce-mixin-3">
  <template>
    <style>
    :host {
      --above: {
        color: rgb(255, 0, 0);
      };
    }
    </style>
    <x-consume-mixin id="child"></x-consume-mixin>
  </template>
  <script>
  HTMLImports.whenReady(function() {
    Polymer({
      is: 'x-produce-mixin-3'
    })
  })
  </script>
</dom-module>

<style is="custom-style">
:root {
  --above: {
    border: 8px solid blue;
  }
}
</style>

<dom-module id="x-apply-depend-before-create">
  <template>
    <style>
    :host {
      @apply --before-create;
    }
    </style>
  </template>
  <script>
  HTMLImports.whenReady(function() {
    Polymer({
      is: 'x-apply-depend-before-create'
    })
  })
  </script>
</dom-module>

<dom-module id="x-create-after-apply">
  <template>
    <style>
    :host {
      --before-create: {
        border: 2px solid green;
      };
    }
    </style>
    <x-apply-depend-before-create id="child"></x-apply-depend-before-create>
  </template>
  <script>
  HTMLImports.whenReady(function() {
    Polymer({
      is: 'x-create-after-apply'
    })
  })
  </script>
</dom-module>

<dom-module id="x-reuse-mixin-name-for-variable">
  <template>
    <style>
    :host {
      /* --foo is used above as a mixin for color */
      --foo: 20px solid blue;
      /* this looks suspiciously like defining a mixin from another mixin */
      --color: var(--foo);
      border: var(--color);
    }
    </style>
  </template>
  <script>
  HTMLImports.whenReady(function() {
    Polymer({
      is: 'x-reuse-mixin-name-for-variable'
    })
  });
  </script>
</dom-module>

<dom-module id="x-apply">
  <template>
    <style>
    :host {
      color: rgb(255, 0, 0);
      border: 1px solid black;
      @apply --initial-inherit;
    }
    </style>
  </template>
  <script>
  HTMLImports.whenReady(function() {
    Polymer({
      is: 'x-apply'
    })
  });
  </script>
</dom-module>

<dom-module id="x-initial">
  <template>
    <style>
    :host {
      color: rgb(0, 255, 0);
      border: 2px solid blue;
      --initial-inherit: {
        color: initial;
        border: initial;
      };
    }
    </style>
    <x-apply id="child"></x-apply>
  </template>
  <script>
  HTMLImports.whenReady(function() {
    Polymer({
      is: 'x-initial'
    })
  });
  </script>
</dom-module>

<dom-module id="x-inherit">
  <template>
    <style>
    :host {
      color: rgb(0, 255, 0);
      border: 2px solid blue;
      --initial-inherit: {
        color: inherit;
        border: inherit;
      };
    }
    </style>
    <x-apply id="child"></x-apply>
  </template>
  <script>
  HTMLImports.whenReady(function() {
    Polymer({
      is: 'x-inherit'
    })
  });
  </script>
</dom-module>

<style is="custom-style">
:root {
  /*--build: {
    color: inherit;
  }*/
  --build_-_color: apply-shim-inherit;
}
x-built {
  color: rgb(123, 123, 123);
}
</style>
<dom-module id="x-built">
  <template>
    <style>
    #child {
      color: rgb(0, 0, 0);
      /* @apply --build */
      color: var(--build_-_color, rgb(0, 0, 0));
    }
    </style>
    <div id="child"></div>
  </template>
  <script>
  HTMLImports.whenReady(function() {
    Polymer({
      is: 'x-built'
    })
  });
  </script>
</dom-module>

<dom-module id="x-apply-order">
  <template>
    <style>
    :host {
      @apply --gets-applied;
      --gets-applied: {
        color: rgb(123, 123, 123);
      };
    }
    :host {
      @apply --doesnt-get-applied;
    }
    :host {
      --doesnt-get-applied: {
        font-size: 100px;
      };
    }
    </style>
  </template>
  <script>
  HTMLImports.whenReady(function() {
    Polymer({
      is: 'x-apply-order'
    })
  });
  </script>
</dom-module>

<script>
suite('scoped-styling-apply', function() {
  function assertComputed(element, value, property) {
    var computed = getComputedStyle(element);
    property = property || 'border-top-width';
    assert.equal(computed[property], value, 'computed style incorrect for ' + property);
  }

  var styled = document.querySelector('x-scope')
  var stylesBuilt;

  suiteSetup(function() {
    stylesBuilt = Polymer.StyleUtil.cssBuildTypeForModule('x-scope');
  });

  test('variable mixins calculated correctly and inherit', function() {
    if (Polymer.Settings.useNativeCSSProperties) {
      this.skip();
    }
    var suffix = '';
    if (stylesBuilt) {
      suffix = Polymer.ApplyShim._separator + 'border';
    }
    var e = styled;
    assert.property(e._styleProperties, '--mixin1' + suffix);
    assert.property(e._styleProperties, '--mixin2' + suffix);
    assert.property(e._styleProperties, '--mixin3' + suffix);
    assert.property(e._styleProperties, '--mixin4' + suffix);
    e = styled.$.child;
    assert.property(e._styleProperties, '--mixin1' + suffix);
    assert.property(e._styleProperties, '--mixin2' + suffix);
    assert.property(e._styleProperties, '--mixin3' + suffix);
    assert.property(e._styleProperties, '--mixin4' + suffix);
  });

  test('variable mixins apply', function() {
    assertComputed(styled.$.mixin1, '1px');
    assertComputed(styled.$.mixin2, '2px');
    assertComputed(styled.$.mixin3, '2px');
    assertComputed(styled.$.mixin3, '1px', 'padding-top');
    assertComputed(styled.$.mixin3, '1px', 'margin-top');
    assertComputed(styled.$.mixin4, '2px');
    assertComputed(styled.$.mixin4, '2px', 'padding-top');
    assertComputed(styled.$.mixin4, '2px', 'margin-top');
  });

  test('mixins apply with url values', function() {
    var url = 'http://www.google.com/icon.png';
    var e = styled.$.child;
    if (Polymer.Settings.useNativeCSSProperties) {
      var propertyName = '--mixin2' + Polymer.ApplyShim._separator + 'background';
      var actual = getComputedStyle(e).getPropertyValue(propertyName);
      // if strings aren't used in the url, getPropertyValue will escape the string, which breaks assert.include
      var unescaped = actual.replace(/\\/g, '');
      assert.include(unescaped, url);
    } else {
      var propName = '--mixin2';
      if (stylesBuilt) {
        propName = propName + Polymer.ApplyShim._separator + 'background';
      }
      assert.include(e._styleProperties[propName], url);
      assert.include(e._customStyle.textContent, url);
    }
  });

  test('variable mixins inherit and override', function() {
    var e = styled.$.child;
    assertComputed(e.$.mixin1, '1px');
    assertComputed(e.$.mixin2, '3px');
    assertComputed(e.$.mixin3, '2px');
    assertComputed(e.$.mixin3, '3px', 'padding-top');
    assertComputed(e.$.mixin4, '2px');
    assertComputed(e.$.mixin4, '2px', 'padding-top');
    assertComputed(e.$.mixin4, '2px', 'margin-top');
  });

  test('calc can be used in mixins', function() {
    assertComputed(styled.$.mixin5, '15px');
  });

  test('mixins work with selectors that contain element name', function() {
    assertComputed(styled.$.card.$.content, '11px');
  });

  test('mixins with trailing new line or } apply', function() {
    assertComputed(styled.$.child.$.mixin6, '16px');
    assertComputed(styled.$.child.$.mixin7, '17px');
  });

  test('mixins with new `@apply --foo` syntax', function() {
    assertComputed(styled.$.child.$.mixin8, '17px');
  });

  test('mixins can be realiased with var()', function() {
    assertComputed(styled.$.child.$.mixin9, '17px');
  })

  test('mixins apply to @keyframe rules', function(done) {
    var xKeyframes1 = styled.$.keyframes1;
    var xKeyframes2 = styled.$.keyframes2;
    var completed = 0;

    [xKeyframes1, xKeyframes2].forEach(function(xKeyframes, index) {
      var target = index === 0 ? '10px' : '20px';
      var onAnimationEnd = function() {
        assert.include(xKeyframes.getComputedStyleValue('left'), target);

        xKeyframes.removeEventListener('animationend', onAnimationEnd);
        xKeyframes.removeEventListener('webkitAnimationEnd', onAnimationEnd);
        xKeyframes.animated = false;
        if (++completed > 1) {
          done();
        }
      };

      xKeyframes.addEventListener('animationend', onAnimationEnd);
      xKeyframes.addEventListener('webkitAnimationEnd', onAnimationEnd);

      xKeyframes.animated = true;
    });
  });

  test('producing a var that consumes another var preserves static styling', function() {
    var d = document.createElement('x-var-produce-via-consume');
    document.body.appendChild(d);
    CustomElements.takeRecords();
    assertComputed(d, '10px');
  });

  test('producing a var that consumes results in static and not dynamic stylesheet', function() {
    var d = document.createElement('x-var-produce-via-consume');
    document.body.appendChild(d);
    CustomElements.takeRecords();
    var styleRoot = d.shadowRoot ? d.shadowRoot : document.head;
    var staticStyle = styleRoot.querySelector('style[scope=x-var-produce-via-consume]');
    assert.ok(staticStyle);
    assert.match(staticStyle.textContent, /display/, 'static style does not contain style content');
    assert.equal(styleRoot.querySelectorAll('style[scope~=x-var-produce-via-consume]').length, 1);
  });

  test('mixin values can be overridden by subsequent concrete properties', function() {
    assertComputed(styled.$.override, '19px');
  });

  test('@apply without parens is included in style cache behavior', function() {
    var e1 = document.createElement('x-cache-host-1');
    var e2 = document.createElement('x-cache-host-2');
    document.body.appendChild(e1);
    document.body.appendChild(e2);
    Polymer.dom.flush();
    assertComputed(e1.$.child, '10px');
    assertComputed(e2.$.child, '1px');
  });

  test('@apply without parens triggers property shim', function() {
    var e = document.createElement('x-no-paren-apply-only');
    document.body.appendChild(e);
    Polymer.dom.flush();
    assertComputed(e.$.child, '2px');
  });

  test('mixin redefinition resets old properties', function() {
    var e = document.createElement('x-redefine-one');
    document.body.appendChild(e);
    Polymer.dom.flush();
    var div = e.$.two.$.three.$.div;
    assertComputed(div, '2px');
    assertComputed(div, '0px', 'height');
  });

  test('Redefined mixins apply new properties for future elements', function() {
    function getStyleText(element) {
      if (Polymer.Settings.useNativeShadow) {
        return element.shadowRoot.querySelector('style').textContent;
      } else {
        var shadyStyle = element._scopeStyle && element._scopeStyle.nextSibling;
        if (shadyStyle) {
          return shadyStyle.textContent;
        }
        return '';
      }
    }
    var parent1 = document.createElement('x-produce-mixin-1');
    var parent2 = document.createElement('x-produce-mixin-2');
    var parent3 = document.createElement('x-produce-mixin-3');
    document.body.appendChild(parent1);
    document.body.appendChild(parent2);
    document.body.appendChild(parent3);
    CustomElements.takeRecords();
    assertComputed(parent1.$.child, 'rgb(255, 0, 0)', 'color');
    assertComputed(parent2.$.child, 'rgb(0, 255, 0)', 'background-color');
    assertComputed(parent1.$.child, '0px');
    assertComputed(parent2.$.child, '0px');
    assertComputed(parent3.$.child, 'rgb(255, 0, 0)', 'color');
    assertComputed(parent3.$.child, '0px');
    if (!stylesBuilt && Polymer.Settings.useNativeCSSProperties && Polymer.Settings.useNativeShadow) {
      // disable test if css build ran
      // the styles will have been preprocessed with all properties for the mixin
      var parent1Text = getStyleText(parent1.$.child);
      var parent2Text = getStyleText(parent2.$.child);
      var parent3Text = getStyleText(parent3.$.child);
      assert.notEqual(parent1Text, parent2Text, 'x-produce-mixin-2 should invalidate x-consume-mixin');
      assert.equal(parent2Text, parent3Text, 'x-produce-mixin-3 should not have invalidated x-consume-mixin');
    }
  });

  test('mixins can be depended on before creation', function() {
    var e = document.createElement('x-apply-depend-before-create');
    document.body.appendChild(e);
    CustomElements.takeRecords();
    var e2 = document.createElement('x-create-after-apply');
    document.body.appendChild(e2);
    CustomElements.takeRecords();
    assertComputed(e2.$.child, '2px');
  });

  test('mixin names can be safely reused for variable definitions', function() {
    var e = document.createElement('x-reuse-mixin-name-for-variable');
    document.body.appendChild(e);
    CustomElements.takeRecords();
    assertComputed(e, '20px');
  });

  test('Mixins can set "inherit" for a property', function() {
    // IE does not support `inherit` as a property value;
    if (navigator.userAgent.match(/Trident/)) {
      this.skip();
    }
    var e = document.createElement('x-inherit');
    document.body.appendChild(e);
    CustomElements.takeRecords();
    assertComputed(e.$.child, 'rgb(0, 255, 0)', 'color');
    if (!Polymer.Settings.useNativeCSSProperties) {
      assertComputed(e.$.child, '2px');
    } else {
      // setting `inherit` on a non-inheritable property is not supported by Apply Shim
      assert.throws(function() {
        assertComputed(e.$.child, '2px');
      })
    }
  });

  test('Mixins can set "initial" for a property', function() {
    // IE does not support `initial` as a property value;
    if (navigator.userAgent.match(/Trident/)) {
      this.skip();
    }
    var e = document.createElement('x-initial');
    document.body.appendChild(e);
    CustomElements.takeRecords();
    assertComputed(e.$.child, 'rgb(0, 0, 0)', 'color');
    assertComputed(e.$.child, '0px');
  });

  test('styleProperties can handle builds processed with applyShim w.r.t "inherit"', function() {
    if (navigator.userAgent.match(/Trident/)) {
      this.skip();
    }
    var e = document.createElement('x-built');
    document.body.appendChild(e);
    CustomElements.takeRecords();
    assertComputed(e.$.child, 'rgb(123, 123, 123)', 'color');
  });

  test('mixin properties are applied no matter their ordering in the stylesheet', function() {
    var e = document.createElement('x-apply-order');
    document.body.appendChild(e);
    CustomElements.takeRecords();
    assertComputed(e, 'rgb(123, 123, 123)', 'color');
    assertComputed(e, '100px', 'font-size');
  });
});

</script>
</body>
</html>
